library(magrittr) # quando der problema com o "%>%"
library('dplyr') # selecao e filtro de dados
library('geosphere') # localizacao geoespacial
library(lubridate) # datas, funções hour, month, wday
library(plotly) # plot dos gráficos
library(knitr) # usada pelo plotly
library(dummies) # cria colunas binárias para variáveis categóricas
library(scales) # normaliza dados rescalando para float de 0 a 1
library(randomForest) # cria rede neural para criar regressão de tempo de viagem
source('preprocessing.R')
source('mapa_calor_ny.R')
train <- read.csv("data_source/train.csv")
# pega apenas as 10 primeiras linhas para teste
df <- head(train, 10000)

Preparação dos dados

Região de saída

df$bairro_saida = mapply(define_bairro, df$pickup_longitude, df$pickup_latitude)
df$bairro_chegada = mapply(define_bairro, df$dropoff_longitude, df$dropoff_latitude)

Adiciona distância Euclidiana calculada a partir das coordenadas (arquivo Preprocessing.R)

df$dist_euclidiana = dist_eucl(df)

Adiciona distância de Manhattan calculada a partir das coordenadas (arquivo Preprocessing.R)

df$dist_manhattan = dist_manh(df)
df$velocidade = df$dist_manhattan / df$trip_duration

Prepara data e hora da paprtida (acho que não precisa da chegada)

Com isso é possível pegar horário de pico e dia da semana

df$pickup_hour <- hour(df$pickup_datetime)
df$pickup_month <- month(df$pickup_datetime)
df$pickup_weekdays <- wday(df$pickup_datetime)

Limpeza de corridas zeradas e limpa corridas muito longas

df %>%
  filter(df$dist_manhattan > 0.5) -> df
df %>%
  filter(df$trip_duration < 10000) -> df

Análises descritivas

Divisão das regiões que separamos em NY:

Regiões NY

Regiões NY

Quantidade de viagens por região de saída e chegada

df %>%
  group_by(bairro_saida) %>%
  count() -> data_plot
plot1 = plot_ly(data= data_plot, x= ~bairro_saida, y= ~n, type = 'bar')
df %>%
  group_by(bairro_chegada) %>%
  count() -> data_plot
plot2 = plot_ly(data= data_plot, x= ~bairro_chegada, y= ~n, type = 'bar')
subplot(plot1, plot2, shareY = T)

Média da velocidade das viagens por região de saída e chegada

df %>%
  group_by(bairro_saida) %>%
  summarize(velocidade_media = mean(velocidade),n()) -> data_plot
plot1 = plot_ly(data= data_plot, x= ~bairro_saida, y= ~velocidade_media, type = 'bar')
df %>%
  group_by(bairro_chegada) %>%
  summarize(velocidade_media = mean(velocidade),n()) -> data_plot
plot2 = plot_ly(data= data_plot, x= ~bairro_chegada, y= ~velocidade_media, type = 'bar')
subplot(plot1, plot2, shareY = T)

Plotar correlação passageiros tempo

p1 = plot_ly(data= df, x= ~passenger_count, y= ~trip_duration, type = 'scatter', mode = 'markers') 
p2 = plot_ly(data= df, x= ~dist_manhattan, y= ~trip_duration, type = 'scatter', mode = 'markers') %>% 
  layout(title="Correlação Num. Passageiros vs. Tempo   |   Correlação Distância vs. Tempo")
subplot(p1, p2)

Média da velocidade das viagens por hora e dia da semana

df %>%
  group_by(pickup_hour) %>%
  summarize(velocidade_media = mean(velocidade),n()) -> data_plot1
plot1 = plot_ly(data= data_plot1, x= ~pickup_hour, y= ~velocidade_media, type = 'bar')
df %>%
  group_by(pickup_weekdays) %>%
  summarize(velocidade_media = mean(velocidade),n()) -> data_plot2
plot2 = plot_ly(data= data_plot2, x= ~pickup_weekdays, y= ~velocidade_media, type = 'bar') %>% 
  layout(title="Horas       |        Dias da Semana") 
subplot(plot1, plot2, shareY = T)

Quantidade de viagens por hora e dia da semana

df %>%
  group_by(pickup_hour) %>%
  count() -> data_plot1
plot1 = plot_ly(data= data_plot1, x= ~pickup_hour, y= ~n, type = 'bar')
df %>%
  group_by(pickup_weekdays) %>%
  count() -> data_plot2
plot2 = plot_ly(data= data_plot2, x= ~pickup_weekdays, y= ~n, type = 'bar') %>% 
  layout(title="Horas       |        Dias da Semana") 
subplot(plot1, plot2)

Plota mapa de calor de New York com ponto de partida da viagem

heat_map_taxi(train)
OGR data source with driver: ESRI Shapefile 
Source: "C:\Users\Bruno Aquino\Documents\Trabalho R\analise_taxi\data_source\mapa_ny", layer: "geo_export_8661594b-4f67-485f-8af1-84a4bd06054d"
with 5 features
It has 4 fields

Normalizar dados para o modelo

Primeiro, criando variáveis dummies para dia da semana e hora

week_dummy = dummy(df$pickup_weekdays, sep='_')
hour_dummy = dummy(df$pickup_hour, sep='_')
bairro_dummy = dummy(df$bairro_chegada, sep='_')
df = data.frame(cbind(df, week_dummy, hour_dummy, bairro_dummy))

Agora normalizando com Min Max Scaler as variáveis: distância, trip_duration e passenger_count

df$dist_manhattan = rescale(df$dist_manhattan)
df$trip_duration = rescale(df$trip_duration)
df$passenger_count = rescale(df$trip_duration)

define X (train features) e y (target) para o treino

X <- df[c('passenger_count', 'dist_manhattan', 'pickup_weekdays_1', 'pickup_weekdays_2', 'pickup_weekdays_3',
         'pickup_weekdays_4', 'pickup_weekdays_5', 'pickup_weekdays_6', 'pickup_weekdays_7', 'pickup_hour_1',
         'pickup_hour_2', 'pickup_hour_3', 'pickup_hour_4', 'pickup_hour_5', 'pickup_hour_6', 'pickup_hour_7',
         'pickup_hour_8', 'pickup_hour_9', 'pickup_hour_10', 'pickup_hour_11', 'pickup_hour_12',
         'pickup_hour_13', 'pickup_hour_14', 'pickup_hour_15', 'pickup_hour_16', 'pickup_hour_17',
         'pickup_hour_18', 'pickup_hour_19', 'pickup_hour_20', 'pickup_hour_21', 'pickup_hour_22',
         'pickup_hour_23', 'bairro_chegada_1', 'bairro_chegada_2', 'bairro_chegada_3', 'bairro_chegada_4',
         'bairro_chegada_5', 'bairro_chegada_6', 'bairro_chegada_7', 'bairro_chegada_8', 'bairro_chegada_9')]
y <- df['trip_duration']

Cria modelo random forest para previsão de tempo de duração das viagens (trip_duration)

f = create_formula(X)
fit <- randomForest(f, df, importance=TRUE, ntree=200)
varImpPlot(fit)

LS0tDQp0aXRsZTogIlRyYWJhbGhvIGZpbmFsIGRlIFIgZG8gZ3J1cG86IEFkcmlhbmEsIEJydW5vLCBSYWZhZWwgZSBWaW7tY2l1cyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQpgYGB7cn0NCmxpYnJhcnkobWFncml0dHIpICMgcXVhbmRvIGRlciBwcm9ibGVtYSBjb20gbyAiJT4lIg0KbGlicmFyeSgnZHBseXInKSAjIHNlbGVjYW8gZSBmaWx0cm8gZGUgZGFkb3MNCmxpYnJhcnkoJ2dlb3NwaGVyZScpICMgbG9jYWxpemFjYW8gZ2VvZXNwYWNpYWwNCmxpYnJhcnkobHVicmlkYXRlKSAjIGRhdGFzLCBmdW7n9WVzIGhvdXIsIG1vbnRoLCB3ZGF5DQpsaWJyYXJ5KHBsb3RseSkgIyBwbG90IGRvcyBncuFmaWNvcw0KbGlicmFyeShrbml0cikgIyB1c2FkYSBwZWxvIHBsb3RseQ0KbGlicmFyeShkdW1taWVzKSAjIGNyaWEgY29sdW5hcyBiaW7hcmlhcyBwYXJhIHZhcmnhdmVpcyBjYXRlZ/NyaWNhcw0KbGlicmFyeShzY2FsZXMpICMgbm9ybWFsaXphIGRhZG9zIHJlc2NhbGFuZG8gcGFyYSBmbG9hdCBkZSAwIGEgMQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpICMgY3JpYSByZWRlIG5ldXJhbCBwYXJhIGNyaWFyIHJlZ3Jlc3PjbyBkZSB0ZW1wbyBkZSB2aWFnZW0NCnNvdXJjZSgncHJlcHJvY2Vzc2luZy5SJykNCnNvdXJjZSgnbWFwYV9jYWxvcl9ueS5SJykNCmBgYA0KDQoNCmBgYHtyfQ0KdHJhaW4gPC0gcmVhZC5jc3YoImRhdGFfc291cmNlL3RyYWluLmNzdiIpDQoNCiMgcGVnYSBhcGVuYXMgYXMgMTAgcHJpbWVpcmFzIGxpbmhhcyBwYXJhIHRlc3RlDQpkZiA8LSBoZWFkKHRyYWluLCAxMDAwMCkNCmBgYA0KDQojIFByZXBhcmHn428gZG9zIGRhZG9zDQoNCiMjIFJlZ2njbyBkZSBzYe1kYQ0KYGBge3J9DQpkZiRiYWlycm9fc2FpZGEgPSBtYXBwbHkoZGVmaW5lX2JhaXJybywgZGYkcGlja3VwX2xvbmdpdHVkZSwgZGYkcGlja3VwX2xhdGl0dWRlKQ0KZGYkYmFpcnJvX2NoZWdhZGEgPSBtYXBwbHkoZGVmaW5lX2JhaXJybywgZGYkZHJvcG9mZl9sb25naXR1ZGUsIGRmJGRyb3BvZmZfbGF0aXR1ZGUpDQoNCmBgYA0KDQojIyBBZGljaW9uYSBkaXN04m5jaWEgRXVjbGlkaWFuYSBjYWxjdWxhZGEgYSBwYXJ0aXIgZGFzIGNvb3JkZW5hZGFzIChhcnF1aXZvIFByZXByb2Nlc3NpbmcuUikNCmBgYHtyfQ0KDQpkZiRkaXN0X2V1Y2xpZGlhbmEgPSBkaXN0X2V1Y2woZGYpDQoNCmBgYA0KDQojIyBBZGljaW9uYSBkaXN04m5jaWEgZGUgTWFuaGF0dGFuIGNhbGN1bGFkYSBhIHBhcnRpciBkYXMgY29vcmRlbmFkYXMgKGFycXVpdm8gUHJlcHJvY2Vzc2luZy5SKQ0KYGBge3J9DQoNCmRmJGRpc3RfbWFuaGF0dGFuID0gZGlzdF9tYW5oKGRmKQ0KZGYkdmVsb2NpZGFkZSA9IGRmJGRpc3RfbWFuaGF0dGFuIC8gZGYkdHJpcF9kdXJhdGlvbg0KYGBgDQoNCiMjIFByZXBhcmEgZGF0YSBlIGhvcmEgZGEgcGFwcnRpZGEgKGFjaG8gcXVlIG7jbyBwcmVjaXNhIGRhIGNoZWdhZGEpDQojIyBDb20gaXNzbyDpIHBvc3PtdmVsIHBlZ2FyIGhvcuFyaW8gZGUgcGljbyBlIGRpYSBkYSBzZW1hbmENCg0KYGBge3J9DQpkZiRwaWNrdXBfaG91ciA8LSBob3VyKGRmJHBpY2t1cF9kYXRldGltZSkNCmRmJHBpY2t1cF9tb250aCA8LSBtb250aChkZiRwaWNrdXBfZGF0ZXRpbWUpDQpkZiRwaWNrdXBfd2Vla2RheXMgPC0gd2RheShkZiRwaWNrdXBfZGF0ZXRpbWUpDQpgYGANCg0KIyMgTGltcGV6YSBkZSBjb3JyaWRhcyB6ZXJhZGFzIGUgbGltcGEgY29ycmlkYXMgbXVpdG8gbG9uZ2FzDQpgYGB7cn0NCmRmICU+JQ0KICBmaWx0ZXIoZGYkZGlzdF9tYW5oYXR0YW4gPiAwLjUpIC0+IGRmDQpkZiAlPiUNCiAgZmlsdGVyKGRmJHRyaXBfZHVyYXRpb24gPCAxMDAwMCkgLT4gZGYNCmBgYA0KDQojIEFu4Wxpc2VzIGRlc2NyaXRpdmFzDQoNCiMjIERpdmlz428gZGFzIHJlZ2n1ZXMgcXVlIHNlcGFyYW1vcyBlbSBOWToNCiFbUmVnafVlcyBOWV0ocmVnaW9lc19ueS5qcGcpDQoNCg0KIyMgUXVhbnRpZGFkZSBkZSB2aWFnZW5zIHBvciByZWdp428gZGUgc2HtZGEgZSBjaGVnYWRhDQpgYGB7cn0NCmRmICU+JQ0KICBncm91cF9ieShiYWlycm9fc2FpZGEpICU+JQ0KICBjb3VudCgpIC0+IGRhdGFfcGxvdA0KcGxvdDEgPSBwbG90X2x5KGRhdGE9IGRhdGFfcGxvdCwgeD0gfmJhaXJyb19zYWlkYSwgeT0gfm4sIHR5cGUgPSAnYmFyJykNCg0KZGYgJT4lDQogIGdyb3VwX2J5KGJhaXJyb19jaGVnYWRhKSAlPiUNCiAgY291bnQoKSAtPiBkYXRhX3Bsb3QNCnBsb3QyID0gcGxvdF9seShkYXRhPSBkYXRhX3Bsb3QsIHg9IH5iYWlycm9fY2hlZ2FkYSwgeT0gfm4sIHR5cGUgPSAnYmFyJykNCg0Kc3VicGxvdChwbG90MSwgcGxvdDIsIHNoYXJlWSA9IFQpDQpgYGANCiMjIE3pZGlhIGRhIHZlbG9jaWRhZGUgZGFzIHZpYWdlbnMgcG9yIHJlZ2njbyBkZSBzYe1kYSBlIGNoZWdhZGENCmBgYHtyfQ0KZGYgJT4lDQogIGdyb3VwX2J5KGJhaXJyb19zYWlkYSkgJT4lDQogIHN1bW1hcml6ZSh2ZWxvY2lkYWRlX21lZGlhID0gbWVhbih2ZWxvY2lkYWRlKSxuKCkpIC0+IGRhdGFfcGxvdA0KcGxvdDEgPSBwbG90X2x5KGRhdGE9IGRhdGFfcGxvdCwgeD0gfmJhaXJyb19zYWlkYSwgeT0gfnZlbG9jaWRhZGVfbWVkaWEsIHR5cGUgPSAnYmFyJykNCg0KZGYgJT4lDQogIGdyb3VwX2J5KGJhaXJyb19jaGVnYWRhKSAlPiUNCiAgc3VtbWFyaXplKHZlbG9jaWRhZGVfbWVkaWEgPSBtZWFuKHZlbG9jaWRhZGUpLG4oKSkgLT4gZGF0YV9wbG90DQpwbG90MiA9IHBsb3RfbHkoZGF0YT0gZGF0YV9wbG90LCB4PSB+YmFpcnJvX2NoZWdhZGEsIHk9IH52ZWxvY2lkYWRlX21lZGlhLCB0eXBlID0gJ2JhcicpDQoNCnN1YnBsb3QocGxvdDEsIHBsb3QyLCBzaGFyZVkgPSBUKQ0KYGBgDQoNCg0KIyMgUGxvdGFyIGNvcnJlbGHn428gcGFzc2FnZWlyb3MgdGVtcG8NCmBgYHtyfQ0KcDEgPSBwbG90X2x5KGRhdGE9IGRmLCB4PSB+cGFzc2VuZ2VyX2NvdW50LCB5PSB+dHJpcF9kdXJhdGlvbiwgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdtYXJrZXJzJykgDQpwMiA9IHBsb3RfbHkoZGF0YT0gZGYsIHg9IH5kaXN0X21hbmhhdHRhbiwgeT0gfnRyaXBfZHVyYXRpb24sIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbWFya2VycycpICU+JSANCiAgbGF5b3V0KHRpdGxlPSJDb3JyZWxh5+NvIE51bS4gUGFzc2FnZWlyb3MgdnMuIFRlbXBvICAgfCAgIENvcnJlbGHn428gRGlzdOJuY2lhIHZzLiBUZW1wbyIpDQpzdWJwbG90KHAxLCBwMikNCmBgYA0KDQojIyBN6WRpYSBkYSB2ZWxvY2lkYWRlIGRhcyB2aWFnZW5zIHBvciBob3JhIGUgZGlhIGRhIHNlbWFuYQ0KYGBge3J9DQpkZiAlPiUNCiAgZ3JvdXBfYnkocGlja3VwX2hvdXIpICU+JQ0KICBzdW1tYXJpemUodmVsb2NpZGFkZV9tZWRpYSA9IG1lYW4odmVsb2NpZGFkZSksbigpKSAtPiBkYXRhX3Bsb3QxDQpwbG90MSA9IHBsb3RfbHkoZGF0YT0gZGF0YV9wbG90MSwgeD0gfnBpY2t1cF9ob3VyLCB5PSB+dmVsb2NpZGFkZV9tZWRpYSwgdHlwZSA9ICdiYXInKQ0KZGYgJT4lDQogIGdyb3VwX2J5KHBpY2t1cF93ZWVrZGF5cykgJT4lDQogIHN1bW1hcml6ZSh2ZWxvY2lkYWRlX21lZGlhID0gbWVhbih2ZWxvY2lkYWRlKSxuKCkpIC0+IGRhdGFfcGxvdDINCnBsb3QyID0gcGxvdF9seShkYXRhPSBkYXRhX3Bsb3QyLCB4PSB+cGlja3VwX3dlZWtkYXlzLCB5PSB+dmVsb2NpZGFkZV9tZWRpYSwgdHlwZSA9ICdiYXInKSAlPiUgDQogIGxheW91dCh0aXRsZT0iSG9yYXMgICAgICAgfCAgICAgICAgRGlhcyBkYSBTZW1hbmEiKSANCg0Kc3VicGxvdChwbG90MSwgcGxvdDIsIHNoYXJlWSA9IFQpDQpgYGANCg0KIyMgUXVhbnRpZGFkZSBkZSB2aWFnZW5zIHBvciBob3JhIGUgZGlhIGRhIHNlbWFuYQ0KYGBge3J9DQpkZiAlPiUNCiAgZ3JvdXBfYnkocGlja3VwX2hvdXIpICU+JQ0KICBjb3VudCgpIC0+IGRhdGFfcGxvdDENCnBsb3QxID0gcGxvdF9seShkYXRhPSBkYXRhX3Bsb3QxLCB4PSB+cGlja3VwX2hvdXIsIHk9IH5uLCB0eXBlID0gJ2JhcicpDQpkZiAlPiUNCiAgZ3JvdXBfYnkocGlja3VwX3dlZWtkYXlzKSAlPiUNCiAgY291bnQoKSAtPiBkYXRhX3Bsb3QyDQpwbG90MiA9IHBsb3RfbHkoZGF0YT0gZGF0YV9wbG90MiwgeD0gfnBpY2t1cF93ZWVrZGF5cywgeT0gfm4sIHR5cGUgPSAnYmFyJykgJT4lIA0KICBsYXlvdXQodGl0bGU9IkhvcmFzICAgICAgIHwgICAgICAgIERpYXMgZGEgU2VtYW5hIikgDQoNCnN1YnBsb3QocGxvdDEsIHBsb3QyKQ0KYGBgDQoNCiMjIFBsb3RhIG1hcGEgZGUgY2Fsb3IgZGUgTmV3IFlvcmsgY29tIHBvbnRvIGRlIHBhcnRpZGEgZGEgdmlhZ2VtDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KaGVhdF9tYXBfdGF4aSh0cmFpbikNCmBgYA0KDQojIE5vcm1hbGl6YXIgZGFkb3MgcGFyYSBvIG1vZGVsbw0KDQojIyBQcmltZWlybywgY3JpYW5kbyB2YXJp4XZlaXMgZHVtbWllcyBwYXJhIGRpYSBkYSBzZW1hbmEgZSBob3JhDQpgYGB7cn0NCndlZWtfZHVtbXkgPSBkdW1teShkZiRwaWNrdXBfd2Vla2RheXMsIHNlcD0nXycpDQpob3VyX2R1bW15ID0gZHVtbXkoZGYkcGlja3VwX2hvdXIsIHNlcD0nXycpDQpiYWlycm9fZHVtbXkgPSBkdW1teShkZiRiYWlycm9fY2hlZ2FkYSwgc2VwPSdfJykNCg0KZGYgPSBkYXRhLmZyYW1lKGNiaW5kKGRmLCB3ZWVrX2R1bW15LCBob3VyX2R1bW15LCBiYWlycm9fZHVtbXkpKQ0KYGBgDQoNCiMjIEFnb3JhIG5vcm1hbGl6YW5kbyBjb20gTWluIE1heCBTY2FsZXIgYXMgdmFyaeF2ZWlzOiBkaXN04m5jaWEsIHRyaXBfZHVyYXRpb24gZSBwYXNzZW5nZXJfY291bnQNCmBgYHtyfQ0KZGYkZGlzdF9tYW5oYXR0YW4gPSByZXNjYWxlKGRmJGRpc3RfbWFuaGF0dGFuKQ0KZGYkdHJpcF9kdXJhdGlvbiA9IHJlc2NhbGUoZGYkdHJpcF9kdXJhdGlvbikNCmRmJHBhc3Nlbmdlcl9jb3VudCA9IHJlc2NhbGUoZGYkdHJpcF9kdXJhdGlvbikNCg0KYGBgDQoNCiMjIGRlZmluZSBYICh0cmFpbiBmZWF0dXJlcykgZSB5ICh0YXJnZXQpIHBhcmEgbyB0cmVpbm8NCmBgYHtyfQ0KWCA8LSBkZltjKCdwYXNzZW5nZXJfY291bnQnLCAnZGlzdF9tYW5oYXR0YW4nLCAncGlja3VwX3dlZWtkYXlzXzEnLCAncGlja3VwX3dlZWtkYXlzXzInLCAncGlja3VwX3dlZWtkYXlzXzMnLA0KICAgICAgICAgJ3BpY2t1cF93ZWVrZGF5c180JywgJ3BpY2t1cF93ZWVrZGF5c181JywgJ3BpY2t1cF93ZWVrZGF5c182JywgJ3BpY2t1cF93ZWVrZGF5c183JywgJ3BpY2t1cF9ob3VyXzEnLA0KICAgICAgICAgJ3BpY2t1cF9ob3VyXzInLCAncGlja3VwX2hvdXJfMycsICdwaWNrdXBfaG91cl80JywgJ3BpY2t1cF9ob3VyXzUnLCAncGlja3VwX2hvdXJfNicsICdwaWNrdXBfaG91cl83JywNCiAgICAgICAgICdwaWNrdXBfaG91cl84JywgJ3BpY2t1cF9ob3VyXzknLCAncGlja3VwX2hvdXJfMTAnLCAncGlja3VwX2hvdXJfMTEnLCAncGlja3VwX2hvdXJfMTInLA0KICAgICAgICAgJ3BpY2t1cF9ob3VyXzEzJywgJ3BpY2t1cF9ob3VyXzE0JywgJ3BpY2t1cF9ob3VyXzE1JywgJ3BpY2t1cF9ob3VyXzE2JywgJ3BpY2t1cF9ob3VyXzE3JywNCiAgICAgICAgICdwaWNrdXBfaG91cl8xOCcsICdwaWNrdXBfaG91cl8xOScsICdwaWNrdXBfaG91cl8yMCcsICdwaWNrdXBfaG91cl8yMScsICdwaWNrdXBfaG91cl8yMicsDQogICAgICAgICAncGlja3VwX2hvdXJfMjMnLCAnYmFpcnJvX2NoZWdhZGFfMScsICdiYWlycm9fY2hlZ2FkYV8yJywgJ2JhaXJyb19jaGVnYWRhXzMnLCAnYmFpcnJvX2NoZWdhZGFfNCcsDQogICAgICAgICAnYmFpcnJvX2NoZWdhZGFfNScsICdiYWlycm9fY2hlZ2FkYV82JywgJ2JhaXJyb19jaGVnYWRhXzcnLCAnYmFpcnJvX2NoZWdhZGFfOCcsICdiYWlycm9fY2hlZ2FkYV85JyldDQoNCnkgPC0gZGZbJ3RyaXBfZHVyYXRpb24nXQ0KYGBgDQoNCiMjIENyaWEgbW9kZWxvIHJhbmRvbSBmb3Jlc3QgcGFyYSBwcmV2aXPjbyBkZSB0ZW1wbyBkZSBkdXJh5+NvIGRhcyB2aWFnZW5zICh0cmlwX2R1cmF0aW9uKQ0KYGBge3J9DQpmID0gY3JlYXRlX2Zvcm11bGEoWCkNCmZpdCA8LSByYW5kb21Gb3Jlc3QoZiwgZGYsIGltcG9ydGFuY2U9VFJVRSwgbnRyZWU9MjAwKQ0KdmFySW1wUGxvdChmaXQpDQpgYGANCg0KDQo=